#ifndef __M_SINK_H__
#define __M_SINK_H__

#include <iostream>
#include <memory>
#include <fstream>
#include <sstream>
#include <assert.h>
#include "Util.hpp"

/*
    将格式化完成的日志消息字符串，输出到指定的位置

    设计思想:
        1.抽象落地基类
        2.派生子类(根据不同的落地方向进行派生)
        3.使用工厂模式进行创建与表示的分离
*/

namespace log
{
    class Sink
    {
    public:
        using ptr = std::shared_ptr<Sink>;
        virtual ~Sink() {} // 将可能会被继承的父类的析构函数设置为虚函数，可以保证当我们new一个子类，然后使用基类指针指向该子类对象，释放基类指针时可以释放掉子类的空间，防止内存泄漏。
        virtual void log(const char* data, size_t len) = 0;
    };
    class Stdoutsink : public Sink
    {
    public:
        using ptr = std::shared_ptr<Stdoutsink>;
        virtual void log(const char* data, size_t len) // 写入
        {
            std::cout.write(data,len);//防止遇到空格就结束
        }
    }; 
    class FileSink : public Sink
    {
    public:
        using ptr = std::shared_ptr<FileSink>;
        FileSink(const std::string &FileName)
            : _FileName(FileName)
        {
            Util::File::Create_Dictory(Util::File::GetDictory(FileName));  // 创建目录
            _ofs.open(FileName.c_str(), std::ios::binary | std::ios::app); // 打开文件,不存在就创建文件
            assert(_ofs.is_open());
        }
        const std::string GetFileName() { return _FileName; }
        virtual void log(const char* data, size_t len) // 写入
        {
            _ofs.write(data,len);
            if (_ofs.good() == false)
            {
                std::cerr << "日志输出失败" << std::endl;
            }
        }

    private:
        std::string _FileName;
        std::ofstream _ofs; // 管理文件
    };
    class RollSink : public Sink
    {
    public:
        using ptr = std::shared_ptr<RollSink>;
        RollSink(std::string basename, size_t max_fsize)
            : _basename(basename), _max_fsize(max_fsize), _cur_fsize(0)
        {
            Util::File::Create_Dictory(Util::File::GetDictory(_basename)); // 创建目录
        }

        virtual void log(const char* data, size_t len)
        {
            InitLogFile();//创建文件名
            _ofs.write(data, len);
            if (_ofs.good() == false)
            {
                std::cerr << "日志输出失败" << std::endl;
            }
            _cur_fsize += len;
        }

    private:

        void InitLogFile()
        {
            if (_ofs.is_open() == false || _cur_fsize >= _max_fsize)
            {
                _ofs.close();                                                  // 关闭旧的文件或者最开始没有打开文件的时候，忽略这条命令
                std::string FileName = GetFileName();                          // 获取新文件名
                _ofs.open(FileName.c_str(), std::ios::binary | std::ios::app); // 打开新的文件
                assert(_ofs.is_open());
                _cur_fsize = 0; // 清理当前文件的大小变量
                return;
            }
            return;
        }

        std::string GetFileName()
        {
            time_t t = time(NULL);
            struct tm tm;
            localtime_r(&t, &tm);
            std::stringstream ss;
            ss << _basename;
            ss << tm.tm_year + 1900;
            ss << tm.tm_mon + 1;
            ss << tm.tm_mday;
            ss << tm.tm_hour;
            ss << tm.tm_min;
            ss << tm.tm_sec;
            ss << ".log";
            return ss.str();
        }

    private:
        std::string _basename; // 文件基础名字
        std::ofstream _ofs;    // 管理文件
        size_t _max_fsize;        // 文件存储的最大大小
        size_t _cur_fsize;        // 文件当前存储的大小
    };

    //因为每个落地方向的构造函数的参数都是不一样的,所以需要用到不定参数
    class SinkFactory
    {
    public:
        template<typename SinkType,typename ...Args>//SinkType表示落地类型,Args表示该落地类型的所有参数
        static Sink::ptr CreateSink(Args&& ...args)//右值引用
        {
            return std::make_shared<SinkType>(std::forward<Args>(args)...);//forward完美转发,后面...表示展开
        }
    };
}

#endif